/**
 *    Copyright (C) 2009 Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */


#include <cubefs/api/exceptions.hpp>
#include <cubefs/api/tools/strings.hpp>

#include <cubefs/core/tools/file.hpp>
#include <cubefs/core/tools/configurator.hpp>

#include <stddef.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sstream>
#include <iostream>

#define CUBEFS_SOURCE "/api-impl/posix/file.cpp:" CUBEFS_AT

namespace cubefs { namespace tools {

using namespace cubefs::api;
using namespace std;

File::FileListType::~FileListType() throw() {
	try {
		for (Int c = 0; c < size(); c++) {
			delete operator [](c);
		}
	}
	catch (...) {}
}

BigInt File::Size() const {
	struct stat buf;
	if (stat(path_.c_str(), &buf) == 0)
	{
		return buf.st_size;
	}
	else {
		throw FileException(CUBEFS_SOURCE, "Can't get file stats:" + String(strerror(errno)), path_);
	}
}

bool is_directory(StringRef name, bool throw_ex) {
	struct stat buf;
	if (stat(name.c_str(), &buf) == 0)
	{
		return S_ISDIR(buf.st_mode);
	}
	else if (throw_ex) {
		throw FileException(CUBEFS_SOURCE, "Can't get file stats:" + String(strerror(errno)), name);
	}
	else {
		return false;
	}
}

bool File::IsDirectory() const {
	return is_directory(path_, true);
}

bool File::IsExists() const {
	struct stat buf;
	if (stat(path_.c_str(), &buf) == 0) {
		return true;
	}
	else {
		return errno != ENOENT;
	}
}

String File::GetAbsolutePath() const {
	if (path_[0] == '/')
	{
		return path_;
	}
	else {
		char buf[8192];
		if (getcwd(buf, sizeof(buf)))
		{
			return String(buf)+"/"+path_;
		}
		else {
			throw FileException(CUBEFS_SOURCE, "Can't get absolute path:" + String(strerror(errno)), path_);
		}
	}
}

bool mkdir(StringRef name) {
	struct stat buf;
	if (stat(name.c_str(), &buf) != 0)
	{
		return ::mkdir(name.c_str(), 0777) == 0;
	}
	else {
		return S_ISDIR(buf.st_mode);
	}
}

bool File::MkDir() const {
	return mkdir(path_);
}

bool File::MkDirs() const {

	typedef String::size_type SizeT;
	SizeT pos = path_[0] == '/' ? 1 : 0;

	while (pos != String::npos && pos < path_.length())
	{
		SizeT idx = path_.find("/", pos);
		if (idx != String::npos)
		{
			if (!mkdir(path_.substr(0, idx)))
			{
				return false;
			}
			pos = idx + 1;
		}
		else {
			if (!mkdir(path_))
			{
				return false;
			}
			else {
				break;
			}
		}
	}
	return true;
}

void File::Rename(StringRef new_name) {
	if (rename(path_.c_str(), new_name.c_str()) != 0)
	{
		throw FileException(CUBEFS_SOURCE, "Can't rename file: " + String(strerror(errno)), new_name);
	}
}

bool File::Delete() const {
	return remove(path_.c_str()) == 0;
}

StringRef File::GetPath() const {
	return path_;
}

String File::GetName() const {
	String::size_type idx = path_.find_last_of('/');
	if (idx == String::npos)
	{
		return path_;
	}
	else if (idx == path_.length() - 1){
		return "";
	}
	else {
		return path_.substr(idx + 1, path_.length() - idx - 1);
	}
}

File::FileListType* File::ReadDir(const File& file)
{
	if (file.IsDirectory())
	{
		FileListType* list = new FileListType();

		DIR *dp;
		struct dirent *ep;

		dp = opendir (file.GetPath().c_str());
		if (dp != NULL)
		{
			while (ep = readdir (dp))
			{
				String name(ep->d_name);
				if (name != "." && name != "..")
				{
					list->push_back(new File(file.GetPath() + "/" + name));
				}
			}

			closedir (dp);
		}
		else {
			throw FileException(CUBEFS_SOURCE, "Can't open directory:", strerror(errno));
		}

		return list;
	}
	else
	{
		throw FileException(CUBEFS_SOURCE, "File is not a directory", file.GetPath());
	}
}

inline String replace(String& text, StringRef from, StringRef to, bool& action)
{
	typedef String::size_type SizeT;
	action = false;
	for(SizeT index=0; index = text.find(from, index), index != String::npos;)
	{
		text.replace(index, from.length(), to);
		index += to.length();
		action = true;
	}
	return text;
}


String File::NormalizePath(StringRef path)
{
	if (path.find("/") == String::npos)
	{
		return path;
	}
	else if (IsEmpty(path)) {
		throw CubefsException(CUBEFS_SOURCE, "Empty string is specified as a path");
	}
	else {
		typedef String::size_type SizeT;

		String buf = path;
		bool action = true;
		while (action)
		{
			buf = replace(buf, "//", "/", action);
		}

		SizeT start_nosp = buf.find_first_not_of(" ");
		SizeT start_slash = buf.find("/");
		if (start_nosp == start_slash && start_nosp != String::npos && start_nosp > 0)
		{
			return buf.substr(start_nosp, buf.length() - start_nosp);
		}
		else {
			return buf;
		}

		return buf;
	}
}

}}
#undef CUBEFS_SOURCE
